package joc.internal.transformer;

import java.util.List;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.Modifier;
import joc.Always;
import joc.Invariant;
import joc.JOCCondition;
import joc.internal.compiler.EmptyExp;
import joc.internal.compiler.StandaloneExp;
import joc.internal.transformer.helper.BackdoorAnnotationLoader;

public class CustomInvariantClassTransformer extends ClassTransformer {

	public CustomInvariantClassTransformer(Transformer transformer) {
		super(transformer);
	}

	@Override
	public void transform(CtClass clazz) throws Exception {
		List<CtClass> consultedTypes = getConsultedTypes(clazz);
		consultedTypes.add(clazz);
		for (CtClass consultedType : consultedTypes) {
			if (consultedType.hasAnnotation(Invariant.class)) {
				String[] invClasses = new BackdoorAnnotationLoader(consultedType).getClassArrayValue(Invariant.class,
						"value");
				for (String invClass : invClasses) {
					applyInvariant(clazz, transformer.getPool().get(invClass));
				}
			}
		}
	}

	private void applyInvariant(CtClass clazz, CtClass invClass) throws Exception {
		if (!invClass.hasAnnotation(JOCCondition.class)) {
			throw new JOCTransformationException("Invariant " + invClass.getName()
					+ " must have annotation JOCCondition.");
		}
		String invClassName = invClass.getName();
		getLogger().info("applying invariant " + invClassName + " on class " + clazz.getName());
		StandaloneExp insertBefore = initializeConditionString(invClassName);
		StandaloneExp insertAfter = new EmptyExp();
		for (CtBehavior behavior : clazz.getDeclaredBehaviors()) {
			if (!Modifier.isStatic(behavior.getModifiers())) {
				for (CtMethod pcMethod : invClass.getDeclaredMethods()) {
					if (pcMethod.hasAnnotation(Always.class)) {
						StandaloneExp callConditionString = callConditionString(invClass, pcMethod, true, behavior,
								clazz);
						insertBefore = insertBefore.append(callConditionString);
						insertAfter = insertAfter.append(callConditionString);
					}
				}
				getLogger().info("insertBefore for " + behavior.getLongName() + ": " + insertBefore);
				getLogger().info("insertAfter for " + behavior.getLongName() + ": " + insertAfter);
				if (behavior instanceof CtConstructor) {
					getLogger().info("at constructor " + behavior.getLongName());
					insertBefore.insertAfter(behavior);
				} else {
					insertAfter.insertAfter(behavior);
					insertBefore.insertBefore(behavior);
				}
			}
		}
	}
}
